﻿using System.Globalization;
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.AspNetCore;

/// <summary>
/// 附件及文件操作类服务
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TAttachment">附件数据类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
public class AttachmentService<TDbContext, TAttachment, TKey>(
    ILogger<AttachmentService<TDbContext, TAttachment, TKey>> logger,
    TDbContext context,
    IDistributedCache cache,
    IHttpContextAccessor httpContextAccessor,
    IFileService fileService,
    HttpSetting httpSetting) :
    DataService<TDbContext, TAttachment, TKey>(logger, context, cache, httpContextAccessor, httpSetting),
    IAttachmentService<TDbContext, TAttachment, TKey>,
    IDataService<TDbContext, TAttachment, TKey>
    where TDbContext : DbContext
    where TAttachment : class, IAttachment<TKey>, new()
    where TKey : IConvertible
{
    protected readonly IFileService _fileService = fileService;
    /// <summary>
    /// 允许上传的文件类型
    /// </summary>
    protected string[] AllowFileExtensions => (_httpSetting.Attachment?.Upload.Extensions ?? AppSettings.DEFAULT_ALLOW_FILE_EXTENSIONS).Split(AppSettings.CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

    /// <summary>
    /// 获取并返回文件名指向的物理文件
    /// </summary>
    /// <param name="fileName">文件名</param>
    /// <returns></returns>
    public virtual async Task<IActionResult> GetFileAsync(string fileName)
    {
        var filePath = await _fileService.GetFileAsync(fileName);
        if (!File.Exists(filePath))
        {
            _logger.LogError($"File {fileName} not found!");
            return new NotFoundResult();
        }

        _logger.LogInformation($"User {UserName} get the file from path <{filePath}> and name <{fileName}> success");
        return new PhysicalFileResult(filePath, ContentType.Default) { FileDownloadName = Path.GetFileName(fileName) };
    }
    /// <summary>
    /// 文件下载, 提供类似于文件夹目录结构的访问方式进行下载
    /// 文件路径只支持一级路径, 适用于访问自动保存的文件或者符合目录规范的文件
    /// 形如 2020-01-01/xxx.jpg
    /// </summary>
    /// <param name="fileName">文件名</param>
    /// <param name="filePath">文件路径(只支持一级路径)</param>
    /// <returns></returns>
    public virtual async Task<TAttachment?> GetAttachmentAsync(string? fileName, string? filePath = default)
    {
        fileName ??= _request.GetRequestOption<string>(nameof(fileName));
        filePath ??= _request.GetRequestOption<string>(nameof(filePath));
        if (string.IsNullOrWhiteSpace(fileName))
        {
            _logger.LogError("缺少必须的文件名!");
            throw new ArgumentNullException(fileName, "缺少必须的文件名!");
        }

        _logger.LogInformation($"User {UserName} get the file from path <{filePath}> and name <{fileName}>");
        filePath = string.IsNullOrWhiteSpace(filePath) ? fileName : Path.Combine(filePath, fileName);
        return await _dbSet.FirstOrDefaultAsync(x => x.Path == filePath).ConfigureAwait(false);
    }
    /// <summary>
    /// 获取附件对应的文件, 适用于文件下载
    /// </summary>
    /// <param name="fileName"></param>
    /// <param name="filePath"></param>
    /// <returns></returns>
    /// <exception cref="BadHttpRequestException"></exception>
    public virtual async Task<IActionResult> GetAttachmentFileAsync(string? fileName, string? filePath = default)
    {
        var attachment = await GetAttachmentAsync(fileName, filePath);
        if (attachment == null || string.IsNullOrWhiteSpace(attachment.Path))
        {
            _logger.LogError($"File {filePath}/{fileName} not found!");
            return new NotFoundResult();
        }

        filePath = _fileService.GetAttachmentPath(attachment.Path);
        if (!File.Exists(filePath))
        {
            _logger.LogError($"File {filePath}/{fileName} not found!");
            return new NotFoundResult();
        }

        _logger.LogInformation($"User {UserName} get the file from path <{filePath}> and name <{fileName}> success");
        return new PhysicalFileResult(filePath, attachment.ContentType ?? ContentType.Default) { FileDownloadName = attachment.Name ?? fileName };
    }
    /// <summary>
    /// 获取文件, 按业务类型和业务主键获取
    /// </summary>
    /// <param name="businessKey">业务主键</param>
    /// <param name="businessType">业务类型</param>
    /// <returns></returns>
    public virtual async Task<IEnumerable<TAttachment>?> GetAttachmentsAsync(TKey businessKey, string? businessType = default)
    {
        businessType ??= _request.GetRequestOption<string>(nameof(businessType));
        _logger.LogInformation($"User {UserName} query the attachment list with businessType {businessType} and businessKey {businessKey}");

        var queryable = GetQueryable(x => x.BusinessKey != null && x.BusinessKey.Equals(businessKey));
        if (!string.IsNullOrWhiteSpace(businessType))
        {
            queryable = queryable.Where(x => x.BusinessType == businessType);
        }

        var attachments = await queryable.ToListAsync();
        _logger.LogInformation($"User {UserName} query the attachment list with businessType {businessType} and businessKey {businessKey} success");
        return attachments;
    }

    /// <summary>
    /// 单个文件上传
    /// </summary>
    /// <param name="file"></param>
    /// <returns></returns>
    public virtual async Task<UploadResult<TAttachment, TKey>> UploadAsync(IFormFile? file = default)
    {
        if (file == null && _request.HasFormContentType && _request.Form.Files.Any())
        {
            file = _request.Form.Files[0];
        }

        if (file == null)
        {
            throw new BadHttpRequestException("没有提交任何文件!");
        }

        var uploadResult = new UploadResult<TAttachment, TKey>
        {
            FileName = file.FileName,
            StatusCode = HttpStatusCode.NotAcceptable
        };

        if (file.Length <= 0)
        {
            uploadResult.Result = "文件内容为空!";
            return uploadResult;
        }

        var fileSize = _httpSetting.Attachment?.Upload.Size ?? AppSettings.DEFAULT_MAX_FILE_SIZE;
        if (file.Length > fileSize)
        {
            uploadResult.Result = $"文件 {file.Name} 大小超出 {fileSize} 单文件最大上传限制!";
            return uploadResult;
        }

        var ext = Path.GetExtension(file.FileName);
        var extUpper = ext.ToUpperInvariant();
        if (!AllowFileExtensions.Any(x => x == extUpper))
        {
            uploadResult.Result = "文件类型禁止上传!";
            return uploadResult;
        }

        var attachment = new TAttachment()
        {
            Name = file.FileName,
            Length = file.Length,
            Extension = ext,
            ContentType = file.ContentType
        };

        var attachmentPath = DateTime.UtcNow.ToString(AppSettings.DEFAULT_DATE_FORMAT, CultureInfo.InvariantCulture);
        var attachmentFile = _fileService.GetAttachmentPath(attachmentPath);
        if (!Directory.Exists(attachmentFile))
        {
            Directory.CreateDirectory(attachmentFile);
        }

        var fileName = KeyGenerator.GetKey() + ext;
        attachment.Path = Path.Combine(attachmentPath, fileName);
        fileName = Path.Combine(attachmentFile, fileName);

        try
        {
            using (var stream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
            {
                await file.CopyToAsync(stream).ConfigureAwait(false);
                stream.Close();
            }

            uploadResult.StatusCode = HttpStatusCode.OK;
            uploadResult.Result = attachment.Path;
            uploadResult.Attachment = attachment;

            _logger.LogInformation($"User {UserName} upload the file <{uploadResult.FileName}> success with result <{uploadResult.Result}>!");

            #region 图片文件创建缩略图
            if (attachment.ContentType.ToUpperInvariant().StartsWith(nameof(SixLabors.ImageSharp.Image).ToUpperInvariant(), StringComparison.InvariantCultureIgnoreCase))
            {
                await _fileService.GetImageThumbnailFileAsync(fileName);
            }
            #endregion
        }
        catch (Exception ex)
        {
            uploadResult.StatusCode = HttpStatusCode.NotAcceptable;
            uploadResult.Result = ex.Message;
            _logger.LogError(ex, $"User {UserName} upload the file <{uploadResult.FileName}> fail with error: <{ex.GetMessage()}>!");
        }

        return uploadResult;
    }
    /// <summary>
    /// 批量文件上传
    /// </summary>
    /// <param name="files"></param>
    /// <returns></returns>
    public virtual async Task<IEnumerable<UploadResult<TAttachment, TKey>>> UploadAsync(IEnumerable<IFormFile>? files = default)
    {
        if ((files == null || (!files.Any())) && _request.HasFormContentType && _request.Form.Files.IsNotNullOrEmpty())
        {
            files = _request.Form.Files;
        }

        if (files == null || (!files.Any()) || files.Sum(x => x.Length) <= 0)
        {
            throw new BadHttpRequestException("没有提交任何文件!");
        }

        var fileSize = _httpSetting.Attachment?.Upload.Total ?? AppSettings.DEFAULT_TOTAL_FILE_SIZE;
        if (files.Sum(x => x.Length) > fileSize)
        {
            throw new BadHttpRequestException($"文件总大小超出 {fileSize} 最大上传限制!");
        }

        var filePath = DateTime.UtcNow.ToString(AppSettings.DEFAULT_DATE_FORMAT, CultureInfo.InvariantCulture);
        var attachmentPath = _fileService.GetAttachmentPath(filePath);
        if (!Directory.Exists(attachmentPath))
        {
            Directory.CreateDirectory(attachmentPath);
        }

        var uploadResults = new List<UploadResult<TAttachment, TKey>>();
        foreach (var file in files)
        {
            uploadResults.Add(await UploadAsync(file));
        }

        if (uploadResults.Count > 0)
        {
            //日志记录本次上传最终结果: 任意一个文件上传成功算成功
            var fileNames = uploadResults.Select(x => x.FileName ?? string.Empty).ToString<string>();
            var results = uploadResults.Select(x => x.Result ?? string.Empty).ToString<string>();
            if (uploadResults.Any(x => x.StatusCode == HttpStatusCode.OK))
            {
                _logger.LogInformation($"User {UserName} upload the files <{fileNames}> success with result <{results}>!");
            }
            else
            {
                _logger.LogWarning($"User {UserName} upload the files <{fileNames}> success with result <{results}>!");
            }
        }

        return uploadResults;
    }
}

/// <summary>
/// 附件及文件操作类服务
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
public class AttachmentService<TDbContext>(
    ILogger<AttachmentService<TDbContext>> logger,
    TDbContext context,
    IDistributedCache cache,
    IHttpContextAccessor httpContextAccessor,
    IFileService fileService,
    HttpSetting httpSetting) :
    AttachmentService<TDbContext, Attachment, string>(logger, context, cache, httpContextAccessor, fileService, httpSetting),
    IAttachmentService<TDbContext>,
    IDataService<TDbContext, Attachment>
    where TDbContext : DbContext;